package com.gewu.advertisement.core

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.AudioManager
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.webkit.WebView
import android.widget.*
import com.gewu.advertisement.core.common.Downloader
import com.gewu.advertisement.core.common.SourceInvalidException
import com.gewu.advertisement.core.common.SupervisorLogScope
import com.gewu.advertisement.core.model.*
import com.gewu.advertisement.core.model.Advertising.TextRollPosition.*
import com.gewu.advertisement.core.model.Advertising.Type.*
import com.gewu.advertisement.core.model.PlayControlType.*
import kotlinx.coroutines.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.math.min


/**
 * 广告播放器
 * 图片,视频,文本,网页都在这里控制播放
 *
 * 同一时间点只能播放一个网页/视频/视频
 * 而文字可以和其他广告资源同时播放
 *
 * @author htt 2021/1/11
 */
class AdvertisementPlayer(
    private val context: Context,
    private val videoView: VideoView,   //视频控件
    private val imageView: ImageView,   //图片控件
    private val webView: WebView, // 网页控件
    private val textLayout: FrameLayout, //文本布局
    private val videoPlayListener: VideoPlayListener
) : CoroutineScope by SupervisorLogScope<AdvertisementPlayer>(Dispatchers.Main) {

    private val tag = "AdvertisementPlayer"
    private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
    private val mediaController = MediaController(context)
    private val downloader = Downloader()
    private val mediaDir =
        File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "gewu.advertisement")

    private var playJob: Job? = null
    private var videoContinuation: Continuation<Unit>? = null
    private var state: PlayState = PlayState.Start


    //图片和视频的播放队列
    private val advertisingQueue = AdvertisingQueue()
    private val advertisingTextQueue = AdvertisingQueue()

    //滚动文字列表
    private val textViews = mutableListOf<AdvertisementTextView>()

    //删除过期文字的任务列表
    private val destroyTextViewJobs = mutableListOf<Job>()

    init {
        mediaController.visibility = View.GONE
        videoView.setMediaController(mediaController)
        mediaController.setMediaPlayer(videoView)
        videoView.setOnCompletionListener {
            Log.i(tag, "play completion")
            videoContinuation?.resume(Unit)
        }
        videoView.setOnErrorListener { _, what, extra ->
            Log.e(tag, "play error what:$what extra:$extra")
            videoContinuation?.resumeWithException(SourceInvalidException("视频播放失败: what:$what extra:$extra"))
            videoContinuation = null
            true
        }
    }

    /**
     * 播放视频 挂起协程直到视频播放结束 或者视频达到播放时长限制
     */
    private suspend fun playVideoWithTimeout(video: Advertising, file: File) {
        if (video.videoPlayDuration <= 0) {
            playVideoSuspend(file)
            return
        }
        withTimeout(
            time = video.videoPlayDuration * 1000, //视频单次播放时长限制
            block = { playVideoSuspend(file) },
            onTimeout = { Log.i(tag, "播放时间达到时长限制,结束播放 $video") }
        )
    }

    /**
     * 恢复视频播放 挂起协程直到视频播放结束 或者视频达到播放时长限制
     */
    private suspend fun resumeVideoWithTimeout(video: Advertising, elapsedTime: Long) {
        if (video.videoPlayDuration <= 0) {
            resumeVideoSuspend(elapsedTime)
            return
        }
        withTimeout(
            time = video.videoPlayDuration * 1000 - elapsedTime,
            block = { resumeVideoSuspend(elapsedTime) },
            onTimeout = { Log.i(tag, "播放时间达到时长限制,结束播放 $video") }
        )
    }

    /**
     * 播放视频 挂起协程直到视频播放结束
     */
    private suspend fun playVideoSuspend(file: File) {
        Log.d(tag, "播放视频: ${file.name}")
        suspendCancellableCoroutine<Unit> {
            videoContinuation = it
            videoView.stopPlayback()
            videoView.setVideoURI(Uri.fromFile(file))
            videoView.visibility = View.VISIBLE
            videoView.start()
            toggleDisplayType(VIDEO)
        }
    }

    /**
     * 恢复视频播放 挂起协程直到视频播放结束
     */
    private suspend fun resumeVideoSuspend(time: Long) {
        suspendCancellableCoroutine<Unit> {
            videoContinuation = it
            videoView.seekTo(time.toInt())
            videoView.visibility = View.VISIBLE
            videoView.start()
        }
    }
    private var timerList  = mutableListOf<ControlTextTimer>()
    /**
     * 设置播放列表
     */
    @Suppress("ConvertTwoComparisonsToRangeCheck")
    fun submitMedia(advertisements: List<Advertising>) {
        playJob?.cancel()
        videoView.stopPlayback()
        videoContinuation = null

        //视频 图片 网页
        val medias = advertisements.filter { it.adType == VIDEO || it.adType == IMAGE || it.adType == HTML }
        advertisingQueue.submit(medias)
        playNextText(advertisements)
        //删除没用的资源文件
        /*val names = medias.map { it.fileName }
        mediaDir.listFiles()?.forEach {
            if (it.name !in names) it.delete()
        }*/

        if (medias.isEmpty()) {
            videoView.visibility = View.GONE
            imageView.visibility = View.GONE
            webView.visibility = View.GONE
        } else {
            //预先下载所有的资源
            launch {
                Log.i(tag, "开始加载资源")
                for (media in medias) {
                    if (media.adType == IMAGE || media.adType == VIDEO) {
                        try {
                            downloader.download(
                                media.adLink,
                                media.fileName,
                                mediaDir.absolutePath,
                                media.adHashCode
                            )
                        } catch (e: Exception) {
                            Log.e(tag, "资源加载失败:$media", e)
                        }
                    }
                }
            }
            playNextAdvertising()
        }
    }

    private fun playNextText(advertisements: List<Advertising>){
        if (timerList.isNotEmpty() && timerList.size > 0) {
            for (timer in timerList) timer.cancel()
            timerList.clear()
        }
         // 文本
        textLayout.removeAllViews()
        textViews.clear()
        destroyTextViewJobs.forEach { it.cancel() }
        advertisements.filter { it.adType == TEXT }
            .forEach {
                val textView = AdvertisementTextView(context, it)
                textView.direction =
                    it.textRollDirection ?: Advertising.TextRollDirection.RIGHT_TO_LEFT
                textView.actualText = it.adText
                textView.textSize = 40f
                textView.scrollSpeed = it.textRollSpeed
                textView.textSize = it.textSize.toFloat()
                textView.visibility = View.VISIBLE
                var color = it.adTextColor
                if (color.isBlank()) color = "#ffffff"
                textView.setTextColor(Color.parseColor(color))
                var bgColor = it.adTextBgColor
                if (bgColor.isBlank()) bgColor = "#80000000"
                bgColor = bgColor.replace("#","#66")
                textView.setBackgroundColor(Color.parseColor(bgColor))
                textViews.add(textView)
                val layoutParams = FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.WRAP_CONTENT
                )
                val gravity = when (it.textRollPosition) {
                    null, TOP -> Gravity.TOP
                    BOTTOM -> Gravity.BOTTOM
                    CENTER -> Gravity.CENTER_VERTICAL
                }
                layoutParams.gravity = gravity
                textLayout.addView(textView, layoutParams)
                textView.init(windowManager)
               // textView.startScroll()

                if (it.expireTime.isNotBlank()) {
                    val job = launch {
                        delay(it.expireTimeMilli)
                        textLayout.removeView(textView)
                        Log.i(tag, "资源已过期: $it")
                    }
                    destroyTextViewJobs.add(job)
                }
                val format = format.get()!!
                if (format == null) return
                Log.i(tag, "playNextText>>>>>:${it.adType}"+"startTime:"+it.startTime +"end:"+ it.endTime)
                var timer = ControlTextTimer(textView,it)
                timerList.add(timer)
                Timer().schedule(timer, Date(),1000)
            }
    }
    /**
     * 恢复播放
     */
    private fun resume(state: PlayState.Pause) {
        Log.i(tag, "开始恢复播放 当前状态:$state")
        val advertising = state.advertising
        this.state = PlayState.Playing(System.currentTimeMillis(), advertising, state.elapsedTime)
        playJob = launch {
            when (advertising.adType) {
                IMAGE -> {
                    val remainingTime = advertising.imageStayTime * 1000 - state.elapsedTime
                    val time = min(advertising.expireTimeMilli, remainingTime)
                    Log.i(tag, "恢复播放 剩余播放时长:${remainingTime}ms 资源到期时间:${advertising.expireTime}")
                    delay(time)
                    playNextAdvertising()
                }
                HTML -> {
                    val remainingTime = advertising.webStayTime * 1000 - state.elapsedTime
                    val time = min(advertising.expireTimeMilli, remainingTime)
                    Log.i(tag, "恢复播放 剩余播放时长:${remainingTime}ms 资源到期时间:${advertising.expireTime}")
                    delay(time)
                    playNextAdvertising()
                }
                VIDEO -> {
                    Log.i(
                        tag,
                        "恢复播放 已播放时长:${state.elapsedTime}ms 限制播放时长:${advertising.videoPlayDuration * 1000}ms 资源到期时间:${advertising.expireTime}"
                    )
                    withTimeout(
                        time = advertising.expireTimeMilli,
                        block = { resumeVideoWithTimeout(advertising, state.elapsedTime) },
                        onTimeout = { Log.i(tag, "视频资源已过期:${advertising.fileName} 播放下一个资源") }
                    )
                    videoPlayListener.complete(advertising)
                    playNextAdvertising()
                }
                else -> {
                    Log.e(tag, "恢复播放视频, 无效的广告类型:${advertising.adType}, 继续播放下一个资源")
                    playNextAdvertising()
                }
            }
        }
        for (textView in textViews) {
            if (textView.isShow == false) continue
            textView.startScroll()
            textView.visibility = View.VISIBLE
        }
    }

    /**
     * 暂停播放
     */
    private fun pause() {
        Log.i(tag, "开始暂停播放 当前状态:$state")
        val state = this.state
        if (state is PlayState.Playing) {
            if (state.advertising.adType == VIDEO) {
                videoView.pause()
            }
            val elapsedTime = System.currentTimeMillis() - state.startTime + state.elapsedTime
            this.state = PlayState.Pause(elapsedTime, state.advertising)
            Log.i(tag, "暂停播放成功 已播放时长:${elapsedTime}ms")
            playJob?.cancel()
        }

        for (textView in textViews) {
            if (textView.isShow == false) continue
            textView.stopScroll()
        }
    }


    /**
     * 停止播放
     */
    private fun stop() {
        val state = this.state
        Log.i(tag, "开始停止播放 当前状态:$state")
        if (state is PlayState.Playing && state.advertising.adType == VIDEO) {
            videoPlayListener.complete(state.advertising)
        }
        videoView.stopPlayback()
        videoView.visibility = View.GONE
        imageView.setImageBitmap(null)
        webView.visibility = View.GONE
        playJob?.cancel()
        this.state = PlayState.Stop

        for (textView in textViews) {
            Log.i(tag, "开始停止播放 当前状态:${textView.direction}")
            textView.stopScroll()
            textView.visibility = View.GONE
        }
    }

    /**
     * 重新开始播放
     */
    private fun restart() {
        val state = this.state
        Log.i(tag, "开始重新播放 当前状态:$state")
        if (state is PlayState.Playing && state.advertising.adType == VIDEO) {
            videoPlayListener.complete(state.advertising)
        }
        playJob?.cancel()
        advertisingQueue.reset()
        this.state = PlayState.Start
        playNextAdvertising()

        for (textView in textViews) {
            if (textView.isShow == false) continue
            textView.startScroll()
            textView.visibility = View.VISIBLE
        }
    }

    /**
     * 播放下一个广告资源 (图片/视频/网页)
     */
    private fun playNextAdvertising() {
        if (state !is PlayState.Playing && state != PlayState.Start) {
            Log.i(tag, "停止播放 当前播放状态:$state")
            return
        }

        playJob = launch {
            val advertising = advertisingQueue.next()
            if (advertising == null) {
                //播放列表为空 说明所有资源都已经过期了
                Log.i(tag, "视频和图片播放列表为空")
                videoView.stopPlayback()
                videoView.visibility = View.GONE
                imageView.visibility = View.GONE
                return@launch
            }else{ //检查是否在可播放时间内
                val format = format.get()!!
                if (format == null) return@launch
                val start = format.parse(advertising.startTime)
                val end = format.parse(advertising.endTime)
                val temp: String = format.format(Date())
                val current: Date = format.parse(temp)
                if (end.after(current) && start.before(current)){
                    controlAdvertTimer?.cancel()
                    controlAdvertTimer = null
                    controlAdvertTimer = ControlAdvertTimer(advertising)
                    Timer().schedule(controlAdvertTimer, end.time - current.time + 1000)
                   Log.d(tag,"规定时间段可播放文件类型: "+advertising.adType)
                }else{
                    videoView.stopPlayback()
                    videoView.visibility = View.GONE
                    imageView.visibility = View.GONE
                    webView.visibility = View.GONE
                    playNextAdvertising()
                    return@launch
                }
            }
            //播放资源
            when (advertising.adType) {
                IMAGE -> {
                    val file = downloadAdvertisingUntilSuccess(advertising)
                    val fileName = advertising.fileName
                    state = PlayState.Playing(System.currentTimeMillis(), advertising)
                    val time = min(advertising.expireTimeMilli, advertising.imageStayTime * 1000L)
                    try {
                        imageView.visibility = View.VISIBLE
                        val bitmap = BitmapFactory.decodeFile(file.absolutePath)
                        imageView.setImageBitmap(bitmap)
                    } catch (e: Exception) {
                        Log.e(tag, "图片解码失败, 已从播放队列中删除图片: $advertising", e)
                        advertisingQueue.remove(advertising)
                        playNextAdvertising()
                        return@launch
                    }
                    toggleDisplayType(IMAGE)
                    Log.i(tag, "播放图片:$fileName")
                    delay(time) //图片停留时间
                    playNextAdvertising()
                }
                HTML -> {
                    state = PlayState.Playing(System.currentTimeMillis(), advertising)
                    val time = min(advertising.expireTimeMilli, advertising.webStayTime * 1000L)
                    toggleDisplayType(HTML)
                    webView.visibility = View.VISIBLE
                    webView.loadUrl(advertising.webUrl)
                    Log.i(tag, "加载网页: ${advertising.adName} ${advertising.webUrl}")
                    delay(time) //网页停留时间
                    playNextAdvertising()
                }
                VIDEO -> {
                    val file = downloadAdvertisingUntilSuccess(advertising)
                    state = PlayState.Playing(System.currentTimeMillis(), advertising)
                    videoPlayListener.start(advertising)
                    try {
                        withTimeout(
                            time = advertising.expireTimeMilli, //视频到期时间
                            block = { playVideoWithTimeout(advertising, file) },
                            onTimeout = { Log.i(tag, "资源已到期 停止该资源播放:$advertising 继续播放下一个资源") }
                        )
                    } catch (e: SourceInvalidException) {
                        //视频播放失败
                        Log.e(tag, "视频播放失败, 已从播放队列中删除视频: $advertising", e)
                        advertisingQueue.remove(advertising)
                    }
                    videoPlayListener.complete(advertising)
                    playNextAdvertising()
                }
                else -> {
                    Log.i(tag, "无效的多媒体类型:$advertising")
                }
            }
        }
    }

    private  var controlAdvertTimer: ControlAdvertTimer? = null
    val format: ThreadLocal<SimpleDateFormat?> = object : ThreadLocal<SimpleDateFormat?>() {
        override fun initialValue(): SimpleDateFormat {
            return SimpleDateFormat("HH:mm:ss")
        }
    }
    inner class ControlAdvertTimer(val advertising: Advertising) : TimerTask() {
        override fun run() {
            val format = format.get()!!
            if (advertising != null && format != null) {
                val start = format.parse(advertising!!.startTime)
                val end = format.parse(advertising!!.endTime)
                val temp: String = format.format(Date())
                val current: Date = format.parse(temp)
                if (current.after(end) || current.before(start)) {
                    when(advertising.adType){
                        IMAGE ->{
                            imageView.setImageBitmap(null)
                        }
                        VIDEO ->{
                            if (state is PlayState.Playing) {
                                videoPlayListener.complete(advertising)
                            }
                            GlobalScope.launch(Dispatchers.Main){
                                videoView.stopPlayback()
                                videoView.visibility = View.GONE
                            }
                        }

                        HTML -> {
                            advertisingQueue.remove(advertising)
                            GlobalScope.launch(Dispatchers.Main) {
                                webView.visibility = View.GONE
                            }
                        }
                    }
                    playNextAdvertising()
                    playJob?.cancel()
                }
            }
        }
    }

    inner class ControlTextTimer(val textView: AdvertisementTextView,val advertising: Advertising) : TimerTask() {
        override fun run() {
            val format = format.get()!!
            if (advertising != null && format != null) {
                if (BuildConfig.DEBUG)Log.d(tag,"======>检查文本显示是否到期 " +advertising!!.startTime +" end:"+ advertising!!.endTime)
                val start = format.parse(advertising!!.startTime)
                val end = format.parse(advertising!!.endTime)
                val temp: String = format.format(Date())
                val current: Date = format.parse(temp)
                if (end.after(current) && start.before(current)) {
                    if (textView.isShow)return
                    GlobalScope.launch(Dispatchers.Main) {
                        textView.isShow = true
                        if (textView.isScroll() == false)textView.startScroll()
                        textView.visibility = View.VISIBLE
                    }
                }else{
                    if (BuildConfig.DEBUG)Log.d(tag,"======>文本显示到期"+textView.isShow)
                    if (textView.isScroll() == false && textView.visibility == View.GONE) return
                    GlobalScope.launch(Dispatchers.Main) {
                            if (textView.isScroll()) textView.stopScroll()
                            textView.isShow = false
                            textView.visibility = View.GONE
                    }
                }
            }
        }
    }
    /**
     * 执行允许设置超时的任务
     * @param time 超时时间
     * @param onTimeout 达到超时时,调用此回调函数
     */
    private suspend inline fun withTimeout(
        time: Long,
        crossinline block: suspend () -> Unit,
        crossinline onTimeout: () -> Unit = {}
    ) {
        try {
            withTimeout(time) {
                block()
            }
        } catch (e: TimeoutCancellationException) {
            onTimeout()
        }
    }

    /**
     * 播放控制
     */
    fun playControl(playControl: PlayControl) {
        val state = this.state
        when (playControl.type) {
            START -> {
                when (state) {
                    is PlayState.Playing -> Log.i(tag, "忽略开始播放命令 当前播放状态:$state")
                    is PlayState.Pause -> resume(state)
                    PlayState.Start, PlayState.Stop -> restart()
                }
            }
            PAUSE -> pause()
            RESTART -> restart()
            STOP -> stop()
            VOLUME -> {
                //音量控制
                val volume = playControl.data
                if (volume == null) {
                    Log.e(tag, "音量值为空 忽略音量控制命令")
                    return
                }
                val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
                val flags = if (BuildConfig.DEBUG) AudioManager.FLAG_SHOW_UI else 0
                val actualVolume = (volume / 100f * maxVolume).toInt()
                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, actualVolume, flags)
            }
        }
    }

    private val Advertising.fileName: String
        get() {
            var suffix = if (adType == VIDEO) ".mp4" else ".jpg"
            val lastIndex = adLink.lastIndexOf('.')
            if (lastIndex != -1) {
                suffix = adLink.substring(lastIndex)
            }
            return "${id}-${adName}$suffix"
        }

    /**
     * 视频播放监听
     */
    interface VideoPlayListener {
        /**
         * 开始播放
         */
        fun start(advertising: Advertising)

        /**
         * 播放完成
         */
        fun complete(advertising: Advertising)
    }

    /**
     * 切换当前要显示的广告类型
     * 文字可以跟其他的类型共存
     * 图片 网页 视频 同一个时间内只能显示一种
     */
    private fun toggleDisplayType(type: Advertising.Type) {
        when (type) {
            IMAGE -> {
                imageView.visibility = View.VISIBLE
                videoView.visibility = View.GONE
                webView.visibility = View.GONE
            }
            VIDEO -> {
                imageView.visibility = View.GONE
                videoView.visibility = View.VISIBLE
                webView.visibility = View.GONE
            }
            HTML -> {
                imageView.visibility = View.GONE
                videoView.visibility = View.GONE
                webView.visibility = View.VISIBLE
            }
            TEXT -> {
            }
        }
    }

    /**
     * 下载资源直到下载成功
     */
    private suspend fun downloadAdvertisingUntilSuccess(advertising: Advertising): File {
        val fileName = advertising.fileName
        if (advertising.adType == IMAGE || advertising.adType == VIDEO) {
            //下载资源 如果下载失败就一直重试 直到下载成功
            while (true) {
                try {
                    return downloader.download(
                        advertising.adLink,
                        fileName,
                        mediaDir.absolutePath,
                        advertising.adHashCode
                    )
                } catch (e: Exception) {
                    Log.e(tag, "资源下载失败 10秒后重试 ad:$advertising", e)
                    delay(10000)
                }
            }
        }
        throw IllegalArgumentException("下载资源失败, 只支持下载图片/视频资源 advertising:$advertising")
    }

}


/**
 * 播放状态
 */
sealed class PlayState {

    object Start : PlayState() {         //准备播放 但还没开始播放
        override fun toString() = "Start"
    }

    data class Playing(
        val startTime: Long,             //开始播放的时间戳
        val advertising: Advertising,    //播放资源
        val elapsedTime: Long = 0L       //已播放的时长 只有当状态从暂停恢复到播放时 会把暂停之前已播放的时长传递到这个值
    ) : PlayState()

    data class Pause(
        val elapsedTime: Long,          //已播放的时长
        val advertising: Advertising    //播放资源
    ) : PlayState()

    object Stop : PlayState() {         //停止播放
        override fun toString() = "Stop"
    }
}

